#!/usr/bin/env python

# -------------------------------------------------------------------------------
# Filename: bioimped_serial.py
# Author:   Ben Han <uha10@uclive.ac.nz>
# Date:     14 Feb 2010
#
# Modified
#	Comment: Interface with Swallowing Rehab Instrument
#           Expects a 12bit digit in the form:
#           <sync byte><low byte><high byte>
# 	Name: Andrew Nicholas
#	Date: Sept 2011
#
# Serial data acquisition module used by biofeedback application
# Adapted from 'miniterm.py' by:
#   (C)2002-2009 Chris Liechti <cliechti@gmx.net>
# -------------------------------------------------------------------------------

import sys, threading, time, traceback
import serial
import glob

class Console():
    baudrate = 115200    # data rate from the device
    timeout = 1         # number of seconds to wait on port operations before timing out

    def __init__(self, update_data, port, s_evt):
        """ The constructor accepts one argument. 'update_data' is a callable object
            which accepts an integer value as an argument.
            port - string representing the port to connect to
        """
        self.update_data = update_data
        self.port = port
        self.serial_loop_closed = s_evt   # event that is set when the reader loop breaks

        try:
            for port in self.scan():
                self.port = port
                
            self.ser_port = serial.Serial(self.port, self.baudrate, timeout=self.timeout)
            sys.stdout.write('>> Serial port %r opened.\n' % self.port)
            self.alive = True

            # Start serial->console thread
            self.receiver_thread = threading.Thread(target=self.reader, name='SerialThread')
            self.receiver_thread.start()
        except serial.SerialException, e:
            sys.stderr.write("%s\n" % e)
            self.stop()
            raise
        
    def scan(self):
        ''' Used to find the corresponding serial port in a mac'''
        return glob.glob('/dev/cu.usb*')

    def stop(self):
        # Stop the while loop, close serial port and wait for current iteration to finish
        self.alive = False
        self.serial_loop_closed.wait()  # wait until the while loop exits
        if self.ser_port:
            try:
                self.ser_port.flush()
            except:
                sys.stderr.write("Error flushing the serial buffer.\n")
                raise
                
            try:
                self.ser_port.close()   # closes the port immediately, ensure read() command is not blocking first
                sys.stdout.write('>> Serial port %r closed.\n' % self.port)
            except:
                sys.stderr.write('\nCould not close serial connection.\n')
                raise
            
    def flush(self):
        # Flush out the serial buffer
        self.ser_port.flush()
        
    def reader(self):
        # The main loop which reads the serial port then passes data to the plotting application.
        # The binary data read from the serial port is decoded by this function.
        hex2bin = { '0':'0000', '1':'0001', '2':'0010', '3':'0011', '4':'0100', '5':'0101', '6':'0110', '7':'0111',
                    '8':'1000', '9':'1001', 'a':'1010', 'b':'1011', 'c':'1100', 'd':'1101', 'e':'1110', 'f':'1111'}

        # Initialise variable values
        index = 0
        high_byte = 0
        low_byte = 0
        last_value = 0
        emglevel = 0

        last_data = '\n'
        sys.stderr.write('Receiver thread started.\n')


        # Clear the serial_loop_closed event flag to indicate that the serial reader loop 
        # is now running and update_data() will be called periodically until the loop ends.
        self.serial_loop_closed.clear()
        
        while self.alive:
        # BEGIN while loop
            try:
                data = self.ser_port.read(1)    # this command will block for <timeout> secs then pass
            except:
                self.alive = False      # close serial channel in event of any error
                if self.ser_port.isOpen():
                    self.ser_port.close()
                sys.stderr.write("Error reading from the serial channel. Port is now CLOSED.\n")
                sys.stderr.write("Please ensure the device connected to the serial port before\n")
                sys.stderr.write("attempting to reconnect.\n")
                break   # end the while loop. This will set the serial_loop_closed event flag to set as it exits.
                
            # escape everything (hexdump)
            for character in data:
                ch = character.encode('hex')
                
                if ch == 'ff':
                    sys.stdout.write('FF: ' + hex(index))
                    
                    if index == 0:    # Check for sync byte
                        emglevel = 0
                        sys.stdout.write(' FF: ' + ch)
                        index = 1
                        break

                if index == 1:
                   low_byte = ord(character)
                   sys.stdout.write(' Low Byte: ' + str(low_byte))
                   index = 2
                   break

                if index == 2:
                   high_byte = ord(character)
                   sys.stdout.write(' High Byte: ' + str(high_byte))

                   emglevel = high_byte << 8
                   sys.stdout.write(' Shift val :' + str(emglevel) + '\n')
                   
                   emglevel += low_byte

                    # Average the last two values and pass the result to update_data()
                  # emglevel = (emglevel + last_value) / 2.0
                   if self.alive:
                       try:
                           self.update_data(emglevel)      # update the data arrays
                           sys.stdout.write('Final val :' + str(emglevel) + '\n')
                       except:
                           print traceback.format_exc()

                   last_value = emglevel

                   index = 0
                else:
                    break
            try:
                sys.stdout.flush()
            except:
                sys.stderr.write("Exception in Console.reader: %s\n%s\n" % (sys.exc_info()[0], sys.exc_info()[1]))
        # END while loop
        
        # Set the serial_loop_closed event flag to indicate that the serial reader loop 
        # is now finished and update_data() will no longer be called.
        self.serial_loop_closed.set()
        sys.stdout.write('Receiver thread ended.\n')
        
# End of myopace_serial.py
